<template>
  <el-space direction="vertical">
    <!-- 上传按钮 -->
    <el-upload :show-file-list="false" :http-request="selectFile" multiple>
      <el-button type="primary">请选择文件</el-button>
    </el-upload>

    <el-button type="primary" @click="handlePause">暂停</el-button>
    <el-button type="primary" @click="handleResume">继续</el-button>
  </el-space>
</template>

<script setup>
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'

import { AUTHORIZATION, CODE_SUCCESS, wsUrl, WAITING_TIME } from "@/common_js/common_variable"
import { getStatus } from '@/network/user/upload'
import { FILE_UPLOAD_SUCCESS, WebSOCKET_TYPE_MERGE, WebSOCKET_TYPE_ERROR, WebSOCKET_TYPE_CHUNK, CHUNK_SIZE } from "@/common_js/userInteface_common"
import { catalogueAdd } from '@/network/user/api_catalogue'
import eventBus from '@/stores/eventBus' // 全局事件，刷新目录使用

// 顶部引入仓库
import { taskList } from '@/stores/UploadTasks'

// 实时响应式目录 id
import { useDirStore } from '@/stores/dirStore'
const dirStore = useDirStore()
const currentDirId = computed(() => dirStore.currentDirId)

// 全局队列
import { storeToRefs } from 'pinia'
import { useTaskQueue } from '@/stores/TaskQueue';
const taskStore = useTaskQueue();
const { queue, paused, runningTasks, pendingList, freeSlots } = storeToRefs(taskStore); /* 需要保持响应式就用 storeToRefs */
const { add, remove, doPause, doResume } = taskStore

// 计算文件 MD5（简易实现，生产环境请放 WebWorker）
import SparkMD5 from 'spark-md5'
async function calcMD5(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = e => resolve(SparkMD5.ArrayBuffer.hash(e.target.result))
    reader.onerror = reject
    reader.readAsArrayBuffer(file)
  })
}

// 选择文件加入到任务队列
async function selectFile({ file }) {
  const fileObj = file
  const taskId = crypto.randomUUID()
  const md5 = await calcMD5(fileObj)// TODO: 测试暂时关闭
  // const md5 = crypto.randomUUID().replaceAll("-",""); // TODO: 测试时打开
  const totalChunks = Math.ceil(fileObj.size / CHUNK_SIZE)
  // console.log(`${fileObj.name}计算MD5结束: ${md5}`);

  let currentChunk = 0;
  const start = currentChunk * CHUNK_SIZE
  const end = Math.min(fileObj.size, start + CHUNK_SIZE)
  const blob = fileObj.slice(start, end)
  const task = {
    type: WebSOCKET_TYPE_CHUNK,
    taskId,
    md5,
    file: fileObj,
    fileName: fileObj.name,
    totalSize: fileObj.size,
    chunkNumber: currentChunk + 1,
    chunkSize: blob.size,
    totalChunks,
    parentId: currentDirId.value,
    status: 0,        // 0 等待 1 进行中 2 成功 3 失败 4 暂停
    uploaded: []
  }

  add(task);
  // console.log(`${fileObj.name}加入队列完毕`);
}

// 全局队列 /* 真正上传：占用一个并发槽，完成后释放 */
async function doUpload(tx) {
  console.log(runningTasks);
  runningTasks.value.add(tx.md5);
  tx.status = 1                    // 进行中
  // console.log(tx);
  const fileObj = tx.file;
  const taskId = tx.taskId;
  const md5 = tx.md5;

  // 1. 先查询是否已有上传记录
  const queryRes = await getStatus(tx.md5);
  const data = queryRes.data;
  console.log(queryRes);

  if (data.state == FILE_UPLOAD_SUCCESS) {
    const newFile = {
      "id": undefined,
      "catalogueId": undefined,
      "type": 1,
      "userId": undefined,
      "name": fileObj.name,
      "parentId": currentDirId.value
    }
    catalogueAdd(newFile).then(res => {
      if (res.code == CODE_SUCCESS) {
        ElMessage.success('文件上传成功');
        eventBus.emit('update-catalogue-list');
      }
      tx.status = 2;
      runningTasks.value.delete(tx.md5);
    })
  } else {
    let token = localStorage.getItem(AUTHORIZATION)
    // const ws = new WebSocket(`wss://your-upload-url/${tx.md5}`)
    const ws = new WebSocket(`${wsUrl}?Authorization=${encodeURIComponent(token)}&fileId=${tx.md5}`)
    await new Promise((resolve, reject) => {
      ws.onopen = async () => {
        for (let curChunk = 0; curChunk < tx.totalChunks; curChunk++) {
          if (tx.uploaded.includes(curChunk)) continue;
          const start = curChunk * CHUNK_SIZE
          const end = Math.min(fileObj.size, start + CHUNK_SIZE)
          const blob = fileObj.slice(start, end)
          /* 【修改点 3】构造 128 字节 JSON 头 + 文件二进制 */
          const headerObj = {
            type: WebSOCKET_TYPE_CHUNK,
            taskId,
            md5,
            fileName: fileObj.name,
            totalSize: fileObj.size,
            chunkNumber: curChunk + 1,
            chunkSize: blob.size,
            parentId: currentDirId.value
          }
          const headerStr = JSON.stringify(headerObj) + '\n'   // 用 \n 做分隔符
          const encoder = new TextEncoder()
          const headerBytes = encoder.encode(headerStr)

          const chunkBytes = new Uint8Array(await blob.arrayBuffer());
          const merged = new Uint8Array(headerBytes.length + chunkBytes.length);
          merged.set(headerBytes, 0);
          merged.set(chunkBytes, headerBytes.length);

          await sendAndDrain(ws, merged);   // ← 串行等待写完
          // const reader = new FileReader()
          // reader.onload = e => {
          //   const chunkBytes = new Uint8Array(e.target.result)
          //   const merged = new Uint8Array(headerBytes.length + chunkBytes.length)
          //   merged.set(headerBytes, 0)
          //   merged.set(chunkBytes, headerBytes.length)

          //   ws.send(merged)   // 一条二进制帧
          // }
          // reader.readAsArrayBuffer(blob)
          console.log(`上传封片${tx.fileName}-${curChunk}`);
        }
        /* 合并指令仍用文本帧，体积小 */
        // ws.send(JSON.stringify({ type: WebSOCKET_TYPE_MERGE, taskId, md5, parentId: currentDirId.value, fileName: fileObj.name }))
        // 所有 chunk 发完再发合并指令
        await sendAndDrain(ws, JSON.stringify({type: WebSOCKET_TYPE_MERGE, taskId, md5, parentId: currentDirId.value, fileName: fileObj.name}));
      }

      ws.onmessage = (e) => {
        // task.value = JSON.parse(e.data)
        const res = JSON.parse(e.data)          // 原始对象
        console.log(`接受消息${tx.fileName}`);
        /* ---------- 新增：统一错误处理 ---------- */
        if (res.type === WebSOCKET_TYPE_ERROR) {
          // ws.close()
          ElMessage.error(res.msg);
          ws.close();
          resolve(); // 关键：释放 Promise
          return
        }
        /* ---------------------------------------- */
        console.log('原始数据', res)

        const idx = taskList.findIndex(t => t.id === taskId)
        if (idx === -1) return

        // 1. 计算百分比
        // const percent = Math.round((res.uploadedSize / res.totalSize) * 100) || 0
        // 2. 判断状态
        // const uploading = res.uploadedSize < res.totalSize
        console.log('进度：', res.percent);
        console.log('状态：', res.uploading);


        // 3. 真正赋值
        Object.assign(taskList[idx], {
          percent: res.percent,
          uploading: res.uploading,
          uploadedSize: res.uploadedSize,
          totalSize: res.totalSize
        })

        console.log('进度：', task.value.percent.toFixed(2) + '%')
        if (task.value.uploading) {
          // go on
        } else {
          ws.close()
          ElMessage.success('上传完成')
          eventBus.emit('update-catalogue-list')
        }
      }
      ws.onerror = (err) => {
        reject(err);
      };
      ws.onclose = () => {
        // 无论成功失败都释放
        runningTasks.value.delete(tx.md5);
      }
    })
  }
}

// 上传文件，需要等待浏览器写完再 resolve 写下一个
/* 工具：真正等浏览器“帧发送完成” */
function sendAndDrain(ws, data) {
  return new Promise((resolve, reject) => {
    try {
      ws.send(data);
      /* 让出事件循环，确保内核把帧送出去 */
      setTimeout(() => resolve(), 0);
    } catch (e) {
      reject(e);
    }
  });
}


const handlePause = () => {
  doPause();
}

const handleResume = () => {
  doResume()
}

// --------------------



/* 监听整个队列变化 */
watch(pendingList, (nows, prev) => {
  console.log(nows);
  // console.log(runningTasks);
  if (freeSlots.value > 0 && nows.length > 0) {
    let task = nows[0];
    doUpload(task);
  }
},
  { deep: true }
)

/* 监听是否全部暂停 */
watch(paused, (p) => {
  console.log('全局已', p ? '暂停' : '继续')
})


</script>